/*
 * Copyright 2007-2009 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package net.paoding.rose.web.impl.context;

import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;

import javax.servlet.ServletContext;

import net.paoding.rose.Rc;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.core.io.Resource;
import org.springframework.util.StringUtils;
import org.springframework.web.context.ConfigurableWebApplicationContext;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
import org.springframework.web.context.support.XmlWebApplicationContext;

/**
 * {@link ContextLoader}参考了Spring提供的
 * {@link org.springframework.web.context.ContextLoader}
 * 类创建Spring上下文环境并作为一个属性设置到ServletContext中。属性名和Spring的ContextLoader规定一样)
 * <p>
 * 创建的这个上下文环境可以通过 {@link Rc#getRootWebApplicationContext()}或
 * {@link WebApplicationContextUtils#getWebApplicationContext(ServletContext)}
 * 获得
 * 
 * @author zhiliang.wang [qieqie.wang@paoding.net]
 * 
 */
public class ContextLoader {

	// 将要装载的这个上下文环境在ServletContext的属性；
	private static final String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE;

	private final Log logger = LogFactory.getLog(getClass());

	// Spring上下文的具体XML配置文件，通过构造函数指定进来
	private String configLocation;

	// 将要装载过来的WebApplicationContext对象
	private XmlWebApplicationContext context;

	/**
	 * 
	 * @param configLocation
	 */
	public ContextLoader(String configLocation) {
		this.configLocation = configLocation;
	}

	/**
	 * 获取在ServletContext中的Spring上下文环境
	 * 
	 * @param servletContext
	 * @return
	 */
	public static XmlWebApplicationContext getRootWebApplicationContext(
			ServletContext servletContext) {
		return (XmlWebApplicationContext) WebApplicationContextUtils
				.getWebApplicationContext(servletContext);
	}

	public static XmlWebApplicationContext getWebApplicationContext(
			ServletContext servletContext, String module) {
		if (module == null) {
			return getRootWebApplicationContext(servletContext);
		}
		return null;
	}

	/**
	 * 根据构造函数设置的配置文件地址，创建Spring应用上下文环境对象，并设置到ServletContext属性中。
	 * <p>
	 * 创建的这个上下文环境可以通过 {@link Rc#getRootWebApplicationContext()}或
	 * {@link WebApplicationContextUtils#getWebApplicationContext(ServletContext)}
	 * 获得
	 * 
	 * @param servletContext
	 * @return
	 * @throws IOException
	 */
	public WebApplicationContext initWebApplicationContext(
			ServletContext servletContext) throws IOException {
		if (servletContext.getAttribute(ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
			throw new IllegalStateException(
					"Cannot initialize context because there is already"
							+ " a root application context present");
		}

		long startTime = System.currentTimeMillis();
		logger.info("Root WebApplicationContext: initialization started");
		servletContext.log("Loading Spring root WebApplicationContext");

		try {
			// 创建Spring application context并设置到ServletContext中，
			// 使用后面可以通过规定属性名从ServletContext获取到这个context对象
			XmlWebApplicationContext wac = (XmlWebApplicationContext) BeanUtils
					.instantiateClass(XmlWebApplicationContext.class);
			wac.setServletContext(servletContext);
			if (configLocation != null) {
				String[] splits = StringUtils
						.tokenizeToStringArray(
								configLocation,
								ConfigurableWebApplicationContext.CONFIG_LOCATION_DELIMITERS);
				List<String> classpathPatterns = new ArrayList<String>();
				for (int i = 0; i < splits.length; i++) {
					if (splits[i].startsWith("classpath-jar:")) {
						classpathPatterns.add(splits[i]
								.substring("classpath-jar:".length()));
						splits[i] = null;
					}
				}
				if (classpathPatterns.size() > 0) {
					List<String> l = new ArrayList<String>();
					for (String split : splits) {
						if (split != null) {
							l.add(split);
						}
					}
					if (wac.getResource("/WEB-INF/lib").exists()) {
						Resource[] jarResources = wac
								.getResources("/WEB-INF/lib/*.jar");
						for (Resource resource : jarResources) {
							URL urlObject = resource.getURL();
							String url = urlObject.toString();
							if (!url.startsWith("jar:")) {
								url = "jar:" + url;
							}
							if (!url.endsWith("!/")) {
								url = url + "!/";
							}
							for (String classpathPattern : classpathPatterns) {
								String location = url + classpathPattern;
								l.add(location);
								if (logger.isDebugEnabled()) {
									logger.debug("add context " + "location: "
											+ location);
								}
							}
						}
					}
					splits = l.toArray(new String[l.size()]);
				}
				wac.setConfigLocations(splits);
			}
			wac.refresh();
			//
			//
			this.context = wac;
			servletContext.setAttribute(ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,
					this.context);

			// 日志打印
			logger.info("Using context class [" + context.getClass().getName()
					+ "] for root WebApplicationContext");
			logger.debug("Published root WebApplicationContext [" + context
					+ "] as ServletContext attribute with name ["
					+ ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
			long elapsedTime = System.currentTimeMillis() - startTime;
			logger.info("Root WebApplicationContext: "
					+ "initialization completed in " + elapsedTime + " ms");
			return this.context;
		} catch (RuntimeException ex) {
			logger.error("Context initialization failed", ex);
			servletContext.setAttribute(ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,
					ex);
			throw ex;
		} catch (Error err) {
			logger.error("Context initialization failed", err);
			servletContext.setAttribute(ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,
					err);
			throw err;
		}
	}

	/**
	 * 关闭Spring应用上下文环境
	 */
	public void closeWebApplicationContext(ServletContext servletContext) {
		servletContext.log("Closing Spring root WebApplicationContext");
		if (this.context != null) {
			this.context.close();
		}

	}
}
